/*
 * Copyright 2022 André Claßen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.burnoutcrew.reorderable

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
/**
 * 创建并记住一个 [ReorderableLazyGridState] 实例，用于支持可拖拽重排序的 LazyGrid。
 *
 * @param onMove 当元素被拖动到新位置时调用。参数为起始位置和目标位置。
 * @param gridState 可选的基础 [LazyGridState]，默认会自动创建。
 * @param canDragOver 决定是否允许将某个项目拖过另一个项目的回调函数。
 * @param onDragStart 拖拽开始时触发的回调，提供起始索引及触摸坐标。
 * @param onDragEnd 拖拽结束时触发的回调，提供起始与结束索引。
 * @param maxScrollPerFrame 每帧最大滚动距离，控制自动滚动速度。
 * @param dragCancelledAnimation 拖拽取消时使用的动画效果，默认使用弹簧动画。
 * @return 返回配置好的 [ReorderableLazyGridState] 实例。
 */
@Composable
fun rememberReorderableLazyGridState(
    onMove: (ItemPosition, ItemPosition) -> Unit,
    gridState: LazyGridState = rememberLazyGridState(),
    canDragOver: ((draggedOver: ItemPosition, dragging: ItemPosition) -> Boolean)? = null,
    onDragStart: ((startIndex: Int, x: Int, y: Int) -> (Unit))? = null,
    onDragEnd: ((startIndex: Int, endIndex: Int) -> (Unit))? = null,
    maxScrollPerFrame: Dp = 20.dp,
    dragCancelledAnimation: DragCancelledAnimation = SpringDragCancelledAnimation()
): ReorderableLazyGridState {
    // 将最大滚动像素值转换为屏幕密度无关单位
    val maxScroll = with(LocalDensity.current) { maxScrollPerFrame.toPx() }
    val scope = rememberCoroutineScope()

    // 记住状态对象以避免重复创建
    val state = remember(gridState) {
        ReorderableLazyGridState(gridState, scope, maxScroll, onMove, canDragOver, onDragStart, onDragEnd, dragCancelledAnimation)
    }

    // 监听可见项变化，并在变化时更新拖拽状态
    LaunchedEffect(state) {
        state.visibleItemsChanged()
            .collect { state.onDrag(0, 0) }
    }

    // 启动协程监听滚动通道并执行实际滚动操作
    LaunchedEffect(state) {
        while (true) {
            val diff = state.scrollChannel.receive()
            gridState.scrollBy(diff)
        }
    }

    return state
}

/**
 * 表示可重新排序的 LazyGrid 的状态管理器。
 *
 * 此类封装了网格布局的状态信息以及处理拖拽逻辑所需的方法，
 * 并实现了 [ReorderableState] 接口来支持拖拽交互。
 *
 * @param gridState 基础的 LazyGrid 状态实例。
 * @param scope 协程作用域，用于启动后台任务。
 * @param maxScrollPerFrame 自动滚动的最大速率（每帧）。
 * @param onMove 元素移动事件的处理器。
 * @param canDragOver 判断是否可以拖拽覆盖某一项的条件。
 * @param onDragStart 拖拽开始时的回调。
 * @param onDragEnd 拖拽完成后的回调。
 * @param dragCancelledAnimation 拖拽中断时播放的动画。
 */
class ReorderableLazyGridState(
    val gridState: LazyGridState,
    scope: CoroutineScope,
    maxScrollPerFrame: Float,
    onMove: (fromIndex: ItemPosition, toIndex: ItemPosition) -> (Unit),
    canDragOver: ((draggedOver: ItemPosition, dragging: ItemPosition) -> Boolean)? = null,
    onDragStart: ((startIndex: Int, x: Int, y: Int) -> (Unit))? = null,
    onDragEnd: ((startIndex: Int, endIndex: Int) -> (Unit))? = null,
    dragCancelledAnimation: DragCancelledAnimation = SpringDragCancelledAnimation()
) : ReorderableState<LazyGridItemInfo>(scope, maxScrollPerFrame, onMove, canDragOver, onDragStart, onDragEnd, dragCancelledAnimation) {

    /**
     * 判断当前滚动方向是否是垂直方向。
     */
    override val isVerticalScroll: Boolean
        get() = gridState.layoutInfo.orientation == Orientation.Vertical

    /**
     * 获取当前项相对于容器左边的距离。
     */
    override val LazyGridItemInfo.left: Int
        get() = offset.x

    /**
     * 获取当前项右边缘的位置。
     */
    override val LazyGridItemInfo.right: Int
        get() = offset.x + size.width

    /**
     * 获取当前项顶部位置。
     */
    override val LazyGridItemInfo.top: Int
        get() = offset.y

    /**
     * 获取当前项底部位置。
     */
    override val LazyGridItemInfo.bottom: Int
        get() = offset.y + size.height

    /**
     * 获取当前项宽度。
     */
    override val LazyGridItemInfo.width: Int
        get() = size.width

    /**
     * 获取当前项高度。
     */
    override val LazyGridItemInfo.height: Int
        get() = size.height

    /**
     * 获取当前项在整个列表中的索引。
     */
    override val LazyGridItemInfo.itemIndex: Int
        get() = index

    /**
     * 获取当前项的唯一标识符。
     */
    override val LazyGridItemInfo.itemKey: Any
        get() = key

    /**
     * 获取当前可视区域内的所有项的信息。
     */
    override val visibleItemsInfo: List<LazyGridItemInfo>
        get() = gridState.layoutInfo.visibleItemsInfo

    /**
     * 获取视口起始偏移量。
     */
    override val viewportStartOffset: Int
        get() = gridState.layoutInfo.viewportStartOffset

    /**
     * 获取视口结束偏移量。
     */
    override val viewportEndOffset: Int
        get() = gridState.layoutInfo.viewportEndOffset

    /**
     * 获取第一个可见项的索引。
     */
    override val firstVisibleItemIndex: Int
        get() = gridState.firstVisibleItemIndex

    /**
     * 获取第一个可见项在其所在行/列中的滚动偏移。
     */
    override val firstVisibleItemScrollOffset: Int
        get() = gridState.firstVisibleItemScrollOffset

    /**
     * 滚动到指定索引的项。
     *
     * @param index 要滚动到的目标项索引。
     * @param offset 在该项内部的额外偏移量。
     */
    override suspend fun scrollToItem(index: Int, offset: Int) {
        gridState.scrollToItem(index, offset)
    }
}
